// --------------------------------------------------------------------------------------------------------------------
// <copyright file="InstrumentListExpoCell.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge
{
    #region Usings

    using System.Collections.Generic;
    using System.Reflection;

    using Org.OpenTrader.Framework.Enums;
    using Org.OpenTrader.Framework.Forge.Enums;
    using Org.OpenTrader.Framework.Forge.Events;
    using Org.OpenTrader.Framework.Forge.Interfaces;

    #endregion

    /// <summary>
    /// Instrument ExpoCell to calculate exposition for a list of instrument
    /// </summary>
    public class InstrumentListExpoCell : InstrumentExpoCell
    {
        #region Constants and Fields

        /// <summary>
        /// The identity.
        /// </summary>
        private static readonly Identity Identity = Identity.Create(MethodBase.GetCurrentMethod());

        /// <summary>
        /// The instruments.
        /// </summary>
        private readonly IList<IInstrument> instruments = new List<IInstrument>();

        #endregion

        /////// <summary>
        /////// The buy average executed price.
        /////// </summary>
        ////private double buyAverageExecutedPrice;
        /////// <summary>
        /////// The buy executed qty.
        /////// </summary>
        ////private int buyExecutedQty;
        /////// <summary>
        /////// The sell average executed price.
        /////// </summary>
        ////private double sellAverageExecutedPrice;
        /////// <summary>
        /////// The sell executed qty.
        /////// </summary>
        ////private int sellExecutedQty;
        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="InstrumentListExpoCell"/> class. 
        /// </summary>
        /// <param name="instrument">
        /// The instrument.
        /// </param>
        /// <param name="type">
        /// The type.
        /// </param>
        public InstrumentListExpoCell(IInstrument instrument, EExpoCellType type)
            : base(instrument)
        {
            // Type
            this.ExpoType = type;

            // Fees Tracker
            this.FeesTracker = new FeesTrackerStandardInstrumentView();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets ExpoType.
        /// </summary>
        public override EExpoCellType ExpoType { get; protected set; }

        /// <summary>
        /// Gets or sets Instrument.
        /// </summary>
        public new IInstrument Instrument
        {
            get
            {
                if (this.instruments.Count > 0)
                {
                    return this.instruments[0];
                }

                return null;
            }
        }

        /// <summary>
        /// Gets InventoryEventDelegate.
        /// </summary>
        public override InventoryEventDelegate InventoryEventDelegate
        {
            get
            {
                return null;
            }
        }

        /// <summary>
        /// Gets NetPL.
        /// </summary>
        public override double NetPL
        {
            get
            {
                return this.PL - (this.FeesTracker != null ? this.FeesTracker.Fees : 0.0);
            }
        }

        /// <summary>
        /// Gets OrderEventDelegate.
        /// </summary>
        public override OrderEventDelegate OrderEventDelegate
        {
            get
            {
                return null;
            }
        }

        /// <summary>
        /// Gets PL.
        /// </summary>
        public override double PL
        {
            get
            {
                this.Reset();
                return this.Realized + this.UnRealized;
            }
        }

        /// <summary>
        /// Gets Realized.
        /// </summary>
        public new double Realized { get; private set; }

        /// <summary>
        /// Gets RefPrice.
        /// </summary>
        public override double RefPrice
        {
            get
            {
                return ExpoRefPriceRule.Standard(this.Instrument);
            }
        }

        /// <summary>
        /// Gets UnRealized.
        /// </summary>
        public new double UnRealized { get; private set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// The add.
        /// </summary>
        /// <param name="i">
        /// The i.
        /// </param>
        /// <returns>
        /// The add.
        /// </returns>
        public bool Add(IInstrument i)
        {
            // Check the instrument
            if (i == null)
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("Add : Cannot add instrument, Unknown Instrument"));
                return false;
            }

            // Check the instrument static data
            if (i.InstrumentStaticData == null)
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("Add : Cannot add instrument, Unknown Instrument Static Data"));
                return false;
            }

            // Add the instrument
            this.instruments.Add(i);

            // Set Fees Instrument
            this.FeesTracker.Instrument = this.Instrument;

            // Set Expo Parent to this
            var expocell = i.Expo[this.ExpoType];
            if (expocell != null)
            {
                expocell.OrderEventCB += this.OnOrderEvent;
                Logging.Log(
                    Identity, 
                    ELogLevel.Debug, 
                    () =>
                    string.Format(
                        "Add : subscribing to OrderEventCB expo : {0} ({1}) : Type = {2}", 
                        i.InstrumentStaticData.Code, 
                        i.InstrumentStaticData.TradingScope, 
                        this.ExpoType));
            }
            else
            {
                Logging.Log(
                    Identity, 
                    ELogLevel.Error, 
                    () =>
                    string.Format(
                        "Add : cannot set expo parent : {0} ({1}) : Type = {2}", i.InstrumentStaticData.Code, i.InstrumentStaticData.TradingScope, this.ExpoType));
            }

            // Debug Log
            Logging.Log(
                Identity, 
                ELogLevel.Debug, 
                () => string.Format("Add : instrument added : {0} ({1})", i.InstrumentStaticData.Code, i.InstrumentStaticData.TradingScope));

            return true;
        }

        /// <summary>
        /// The remove.
        /// </summary>
        /// <param name="i">
        /// The i.
        /// </param>
        /// <returns>
        /// The remove.
        /// </returns>
        public bool Remove(IInstrument i)
        {
            // Check the instrument
            if (i == null)
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("Remove : Cannot remove instrument, Unknown Instrument"));
                return false;
            }

            // Check the instrument static data
            if (i.InstrumentStaticData == null)
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("Remove : Cannot remove instrument, Unknown Instrument Static Data"));
                return false;
            }

            // Remove the instrument
            this.instruments.Remove(i);

            // Set Fees Instrument
            this.FeesTracker.Instrument = this.Instrument;

            // Set Expo Parent to null
            var expocell = i.Expo[this.ExpoType];
            if (expocell != null)
            {
                expocell.OrderEventCB -= this.OnOrderEvent;

                Logging.Log(
                    Identity, 
                    ELogLevel.Debug, 
                    () =>
                    string.Format(
                        "Remove : Unsubscribing OrderEventCB expo : {0} ({1}) : Type = {2}", 
                        i.InstrumentStaticData.Code, 
                        i.InstrumentStaticData.TradingScope, 
                        this.ExpoType));
            }
            else
            {
                Logging.Log(
                    Identity, 
                    ELogLevel.Error, 
                    () =>
                    string.Format(
                        "Remove : cannot remove expo parent : {0} ({1}) : Type = {2}", 
                        i.InstrumentStaticData.Code, 
                        i.InstrumentStaticData.TradingScope, 
                        this.ExpoType));
            }

            // Debug Log
            Logging.Log(
                Identity, 
                ELogLevel.Debug, 
                () => string.Format("Remove : instrument removed : {0} ({1})", i.InstrumentStaticData.Code, i.InstrumentStaticData.TradingScope));

            return true;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Fees notify
        /// </summary>
        /// <param name="fees">
        /// </param>
        private void FeesNotify(double fees)
        {
            if (this.FeesTracker != null)
            {
                ((FeesTrackerStandardInstrumentView)this.FeesTracker).Notify(fees);
            }
        }

        /// <summary>
        /// OnOrderEvent
        /// </summary>
        /// <param name="sender">
        /// </param>
        /// <param name="args">
        /// </param>
        private void OnOrderEvent(object sender, OrderEventArgs args)
        {
            if (sender == null)
            {
                Logging.Log(
                    Identity, ELogLevel.Error, () => string.Format("OnOrderEvent : Cannot notify instument view expo cell {0}, Invalid sender", this.ExpoType));
                return;
            }

            if (sender as InstrumentExpoCell == null)
            {
                Logging.Log(
                    Identity, ELogLevel.Error, () => string.Format("OnOrderEvent : Cannot notify instument view expo cell {0}, Invalid sender", this.ExpoType));
                return;
            }

            if ((sender as InstrumentExpoCell).ExpoType != this.ExpoType)
            {
                Logging.Log(
                    Identity, 
                    ELogLevel.Error, 
                    () =>
                    string.Format(
                        "OnOrderEvent : Cannot notify instument view expo cell {0}, Invalid Expo cell type of the sender {1}", 
                        this.ExpoType, 
                        ((InstrumentExpoCell)sender).ExpoType));
                return;
            }

            // Update local params
            this.IsChanged = true;
            this.LastUpdate = Timings.Now;

            // Forward event securely to subscriber
            this.ForwardOrderEvent(this, args);

            // Logging
            Logging.Log(
                Identity, 
                ELogLevel.Debug, 
                () => string.Format("OnOrderEvent : received event {0} : {1} - {2}", this.LastUpdate, this.Instrument.InstrumentStaticData.Code, this.ExpoType));
        }

        /// <summary>
        /// The reset.
        /// </summary>
        private void Reset()
        {
            // if (!this.IsChanged)
            // {
            // return;
            // }

            // if (this.IsUpdating)
            // {
            // return;
            // }

            // if (this.IsUpdated)
            // {
            // return;
            // }

            // this.IsUpdating = true;
            // this.IsUpdated = false;

            // Init Expo
            this.Init();

            // Init P&L
            this.Realized = 0.0;
            this.UnRealized = 0.0;

            // Init Fees
            if (this.FeesTracker != null)
            {
                ((FeesTrackerStandardInstrumentView)this.FeesTracker).Init();
            }

            // Loop instruments
            foreach (var instrument in this.instruments)
            {
                if (instrument != null)
                {
                    var expocell = instrument.Expo[this.ExpoType];
                    if (expocell != null)
                    {
                        // Add Buy
                        if (expocell.BuyExecutedQty > 0)
                        {
                            this.Add(EOrderWay.Buy, expocell.BuyExecutedQty, expocell.BuyAverageExecutedPrice);
                        }

                        // Add Sell
                        if (expocell.SellExecutedQty > 0)
                        {
                            this.Add(EOrderWay.Sell, expocell.SellExecutedQty, expocell.SellAverageExecutedPrice);
                        }

                        // Fees
                        var fees = expocell.FeesTracker != null ? expocell.FeesTracker.Fees : 0.0;
                        this.FeesNotify(fees);

                        // PNL
                        this.Realized += expocell.Realized;
                        this.UnRealized += expocell.UnRealized;
                    }
                }
            }

            // this.IsChanged = false;
            // this.IsUpdated = true;
            // this.IsUpdating = false;
        }

        #endregion
    }
}